<?php

namespace PHPSpring\Annotation\Impl;

use PHPSpring\Core\ProxyFactory;
use PHPSpring\Core\RuntimeAnnotation;
use PHPSpring\Utils\Utils;
use PHPSpring\Annotation\Interceptor;
use PHPSpring\Core\AOP;
use PHPSpring\Core\BizException;

class Controller {

  private static $RequestMethods = ['RequestMapping', 'Get', 'Post', 'Delete', 'Options', 'Patch', 'Post', 'Put'];

  public static function init ($callback) {
    if (!is_callable($callback)) {
      return;
    }
    $allAnnos = RuntimeAnnotation::getAnnotations();
    if (empty($allAnnos)) {
      return;
    }
    foreach ($allAnnos as $clz => $clzAnnos) {
      $classAnnos = $clzAnnos['class'];
      if (empty($classAnnos['annos'])
        || !isset($classAnnos['annos']['Controller'])) {
        continue;
      }
      list ($pathInClass, $classRequestMethod) = self::getRequestConfigFrom($classAnnos['annos']);
      $methodsAnnos = $clzAnnos['methods'];
      foreach ($methodsAnnos as $method => $methodAnnos) {
        if (empty($methodAnnos['annos'])) {
          continue;
        }
        list ($pathInMethod, $methodRequestMethod) = self::getRequestConfigFrom($methodAnnos['annos']);
        if (empty($pathInMethod)) {
          continue;
        }
        $controller = $classAnnos['type'];
        $methodArgs = [];
        if (!empty($methodAnnos['params'])) {
          foreach ($methodAnnos['params'] as $p) {
            $methodArgs[] = [
              'name'      => $p['name'],
              'type'      => $p['type'],
              'default'   => $p['default'],
              'isPrimary' => Utils::isPrimaryType($p['type'])];
          }
        }
        $requestMethod = ['get'];
        if (!empty($classRequestMethod)) {
          $requestMethod = $classRequestMethod;
        }
        if (!empty($methodRequestMethod)) {
          $requestMethod = $methodRequestMethod;
        }

        $interceptors = AOP::getInterceptors($clzAnnos, $methodAnnos['name']);

        $callback($requestMethod, $pathInClass.$pathInMethod, $methodArgs, function ($requestParams) use ($interceptors, $methodAnnos, $controller) {
          $ins = ProxyFactory::getProxy($controller);
          $method = $methodAnnos['name'];
          $funcParams = $methodAnnos['params'];
          $params = [];
          if (!empty($methodAnnos['params'])) {
            foreach ($methodAnnos['params'] as $p) {
              $v = self::setValue($requestParams, $p['name'], $p['type'], $p['default']);
              $params[] = $v;
            }
          }
          $res = null;
          $ept = null;
          try {
            $res = $ins->$method(...$params);
          } catch (BizException $e) {
            $ept = $e;
          }
          if (isset($interceptors[Interceptor::RESPONSE]) && !empty($interceptors[Interceptor::RESPONSE])) {
            foreach ($interceptors[Interceptor::RESPONSE] as $interceptor) {
              list($interceptorClz, $interceptorArgs) = $interceptor;
              $interceptorMethod = Interceptor::RESPONSE;
              $res = $interceptorClz::$interceptorMethod($interceptorArgs, $res, $ept, $ins, $method, $params);
            }
          }
          if (is_object($res)) {
            $res = json_encode($res);
          }

          return $res;
        });
      }
    }
  }

  public static function getRequestConfigFrom ($annotation) {
    $requestPath = '';
    $requestMethods = [];
    foreach (self::$RequestMethods as $rm) {
      if (isset($annotation[$rm])) {
        $annotationArgs = $annotation[$rm][1];
        $argsLen = count($annotationArgs);
        if ($argsLen > 0) {
          $requestPath = '/'.trim($annotationArgs[0], '/');
          if ($rm == 'RequestMapping') {
            for ($i = 1; $i < $argsLen; $i++) {
              if (in_array($annotationArgs[$i], self::$RequestMethods)) {
                $requestMethods[] = strtolower($annotationArgs[$i]);
              }
            }
          } else {
            $requestMethods[] = strtolower($rm);
          }
        }
        break;
      }
    }

    return [$requestPath, $requestMethods];
  }

  private static function setValue ($params, $name, $type, $default = null) {
    if (Utils::isPrimaryType($type)) {
      if (!isset($params[$name])) {
        return $default;
      }
      $v = $params[$name];
      if (in_array($type, ['int', 'float'])) {
        if (!is_numeric($v)) {
          throw new \InvalidArgumentException("Numeric type accepted. Input was $v");
        }
        $v = $v - 0;
      } elseif ($type == 'bool') {
        $v = $v ? true : false;
      }

      return $v;
    } elseif (class_exists($type)) {
      $valueExist = false;

      $re = new \ReflectionClass($type);
      $constructor = $re->getConstructor();
      if ($constructor) {
        $allParametersCount = $constructor->getNumberOfParameters();
        if ($allParametersCount) {
          //通过构造函数方式设置值
          $ins = new $type($params[$name] ?? '');
          $valueExist = true;
        } else {
          $ins = new $type;
        }
      } else {
        $ins = new $type;
      }

      //只允许设置公有和保护的属性
      $properties = $re->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED);
      foreach ($properties as $property) {
        $pt = Utils::typeToString($property->getType());
        $pt = ltrim($pt, '?');
        if (!isset($params[$property->name]) && Utils::isPrimaryType($pt)) {
          continue;
        }
        $v = self::setValue($params, $property->name, $pt);
        if ($v === null) {
          continue;
        }
        $valueExist = true;
        $property->setAccessible(true);
        $property->setValue($ins, $v);
      }
      if (!$valueExist) {
        return null;
      }

      return $ins;
    }
  }

}
